/*
 * parse.c
 *
 * $Revision: 1.8 $
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <errno.h>
#include "decl.h"
#include "code.h"

/****************************************
  constant definition
****************************************/
#define EOL		'\0'
#define COMMENT_CHAR	';'
#define COMMENT_CHAR1	'#'
#define COMMENT_CHAR2	'/'
#define TAB_CHAR	(0x09)
#define CR_CHAR		(0x0d)
#define LF_CHAR		(0x0a)
#define SPACE_CHAR	' '
#define REGSEP_CHAR	','	/* レジスタ書き込みセパレータ */
#define EOF_CHAR	(0x1a)	/* for Microsoft(damned) */

#define ISSPACE(c)	(isspace(c) || ((c) == EOF_CHAR))
#define FIDX_MAX	(10)

/****************************************
  変数いろいろ
****************************************/
static int octave[MAXTRACK];
static int notelen[MAXTRACK];
static int mvolume = DEFAULT_VOLUME;
static int chvol[MAXTRACK];
static int quant[MAXTRACK];
static int tracknum;		/* 作業しているトラック */
static char *nowline; /* 解析中の入力行 */

/****************************************
  エラー行表示
****************************************/
volatile static void
showerrline(char *line)
{
    int i, count;

    fprintf(stderr, "%s", nowline);
    if (*(nowline + strlen(nowline) - 1) != '\n')
      fputc('\n', stderr);
    for (count = 0, i = 0; i < line - nowline; i++) {
	if (nowline[i] != TAB_CHAR) {
	    fputc('-', stderr);
	    count++;
	} else {
	    fwrite("--------", sizeof(char), 8 - (count % 8), stderr);
	    count += 8 - (count % 8);
	}
    }
    fprintf(stderr, "^\n");
    exit(EXIT_FAILURE);
}

/****************************************
  数値取得
****************************************/
static char *
get_number(long *val, char *line)
{
    char *p = line;
    char *bp, *numbuf;

    numbuf = xmalloc(LINEBUFSIZ);
    bp = numbuf;
    while (isdigit(*p))
      *bp++ = *p++;
    *bp = EOL;
    *val = atoi(numbuf);
    free(numbuf);
    return p;
}

/****************************************
  コマンド取得
****************************************/
static char *
get_command(char *line)
{
    CODE *codep;
    char *lp = line, *newlp;
    char *nbp, notebuf[10];
    long val, nlen, futen[10], fidx = 0;
    long reg, regval;		/* C6230 レジスタ書き込み用 */

#ifdef DEBUG
    if (! *lp)
      goto err;
#endif

    switch (*lp) {
      case COMMENT_CHAR:	/* コメント */
	lp = line + strlen(line);
	break;
      case '@':			/* 音色切り替え */
	newlp = get_number(&val, ++lp);
	if (newlp == lp || val < PROG_MIN || val > PROG_MAX) {
	    fprintf(stderr, "%s: %ld: illegal program\n", infile, inpline);
	    showerrline(lp);
	}
#ifdef PDEBUG
printf("DEBUG program = %ld\n", val);
#endif
	lp = newlp;
	putbuf(tracknum, CODE_PROG, val);
	break;
      case 't':			/* tempo */
      case 'T':
	newlp = get_number(&val, ++lp);
	/* テンポチェック */
	if (newlp == lp || val < TEMPO_MIN || val > TEMPO_MAX) {
	    fprintf(stderr, "%s: %ld: illegal tempo\n", infile, inpline);
	    showerrline(line);
	}
#ifdef PDEBUG
printf("DEBUG tempo = %ld\n", val);
#endif
	lp = newlp;
	putbuf(tracknum, CODE_TEMPO, tempo(val));
	break;
      case 'o':			/* octave */
      case 'O':
	newlp = get_number(&val, ++lp);
	/* オクターブチェック */
	if (newlp == lp)
	  goto err;
	if (val < OCTAVE_MIN)
	  goto octave_uferr;
	if (val > OCTAVE_MAX)
	  goto octave_oferr;
	octave[tracknum] = val;
#ifdef PDEBUG
printf("DEBUG default octave = %ld\n", val);
#endif
	lp = newlp;	  
	break;
      case 'l':			/* default note length */
      case 'L':
	newlp = get_number(&val, ++lp);
	/* 音長チェック */
	if (newlp == lp || val < NOTELEN_MIN || val > NOTELEN_MAX) {
	    fprintf(stderr, "%s: %ld: illegal default length\n", infile, inpline);
	    showerrline(line);
	}
	notelen[tracknum] = val;
#ifdef PDEBUG
printf("DEBUG default note length = %ld\n", val);
#endif
	lp = newlp;
	break;
      case 'V':			/* master volume */
	newlp = get_number(&val, ++lp);
	/* マスターボリュームチェック */
	if (newlp == lp || val < VOLUME_MIN || val > VOLUME_MAX) {
	    fprintf(stderr, "%s: %ld: illegal master volume\n", infile, inpline);
	    showerrline(line);
	}
	lp = newlp;
	mvolume = val;
#ifdef PDEBUG
printf("DEBUG master volume = %ld\n", val);
#endif
	putbuf(tracknum, CODE_VOLUME, VOLUME(val, val));
	break;
      case 'v':			/* channel volume */
	newlp = get_number(&val, ++lp);
	/* チャンネルボリュームチェック */
	if (newlp == lp || val < CHVOL_MIN || val > CHVOL_MAX) {
	    fprintf(stderr, "%s: %ld: illegal channel volume\n", infile, inpline);
	    showerrline(line);
	}
	lp = newlp;
	chvol[tracknum] = val;
#ifdef PDEBUG
printf("DEBUG channel volume = %ld\n", val);
#endif
	putbuf(tracknum, CODE_CHVOL, val);
	break;
      case 'q':			/* 発声割合率指定 */
      case 'Q':
	newlp = get_number(&val, ++lp);
	/* 発声割合チェック */
	if (newlp == lp || val < QUANT_MIN || val > QUANT_MAX) {
	    fprintf(stderr, "%s: %ld: illegal quantization\n", infile, inpline);
	    showerrline(line);
	}
	lp = newlp;
	quant[tracknum] = val;
#ifdef PDEBUG
printf("DEBUG default quantize = %ld\n", val);
#endif
	break;
      case 'c':			/* note */
      case 'd':
      case 'e':
      case 'f':
      case 'g':
      case 'a':
      case 'b':
      case 'C':
      case 'D':
      case 'E':
      case 'F':
      case 'G':
      case 'A':
      case 'B':
      case 'r':			/* 休符 */
      case 'R':
	/* ノートを決定するための検索バッファに文字列を設定する */
	nbp = notebuf;
	*nbp++ = tolower(*lp);	/* ハッシュのため小文字で */
	switch (*(lp + 1)) {	/* ＃♭か? */
	  case '+':
	  case '-':
	  case '#':
	    if (*notebuf == 'r') /* 休符なのに指定されてる!! */
	      goto err;
	    *nbp++ = *(lp + 1);
	    break;
	}
	*nbp = EOL;
	/* 検索バッファからノート番号を生成した CODE_NOTE をバッファに書く */
#ifdef PDEBUG
printf("DEBUG note [o%d%s]\n", octave[tracknum], notebuf);
#endif
	if (*notebuf == 'r') {	/* 休符だったらキーオフ */
	    putbuf(tracknum, CODE_KEYOFF);
	} else {
	    /* ハッシュ表から探す */
	    long note;

	    if ((codep = in_note_set(notebuf, strlen(notebuf))) == NULL) {
		fprintf(stderr, "%s: %ld: internal hash error[%s]\n", infile, inpline, notebuf);
		exit(EXIT_FAILURE);
	    }
	    note = codep->note + (octave[tracknum] - 1) * 12 + 24;
#ifdef PDEBUG
printf("DEBUG note %ld\n", note);
#endif
	    /* 音階チェック */
	    if (note < NOTE_MIN)
	      goto octave_uferr;
	    if (note > NOTE_MAX)
	      goto octave_oferr;
	    putbuf(tracknum, CODE_NOTE, note);
	}

	lp += strlen(notebuf);
	newlp = get_number(&val, lp);
	if (newlp != lp) {	/* 音長が指定されている */
	    lp = newlp;
	} else {		/* デフォルト音長 */
	    val = notelen[tracknum];
	}
	/* 符点処理 */
	if (*lp == '.') {
	    futen[fidx++] = val * 2;
	    while (*lp == '.') {
		futen[fidx] = futen[fidx - 1] * 2;
		/* 符点数チェック */
		if (++fidx > FIDX_MAX) {
		    fprintf(stderr, "%s: %ld: illegal futen number\n", infile, inpline);
		    showerrline(line);
		}
		lp++;
	    }
	}
	
	/* 音長計算 */
	if (val < NOTELEN_MIN || val > NOTELEN_MAX) {
	    fprintf(stderr, "%s: %ld: illegal note length\n", infile, inpline);
	    showerrline(line);
	}
	nlen = TIMEBASE * 4 / val;
	if (fidx) {
	    int i;

	    for (i = 0; i < fidx - 1; i++) {
#ifdef PDEBUG
printf("DEBUG futen %ld\n", TIMEBASE * 4 / futen[i]);
#endif
		nlen += TIMEBASE * 4 / futen[i];
	    }
	}
#ifdef PDEBUG
printf("DEBUG wait  %ld\n", nlen);
#endif
	/* 発声割合率による音長再計算 */
	if (quant[tracknum] != DEFAULT_QUANT && *notebuf != 'r') { /* 休符 */
	    putbuf(tracknum, CODE_WAIT, nlen * quant[tracknum] / DEFAULT_QUANT);
	    putbuf(tracknum, CODE_KEYOFF);
	    putbuf(tracknum, CODE_WAIT, nlen - nlen * quant[tracknum] / DEFAULT_QUANT);
	} else {
	    /* 発声割合10割 */
	    putbuf(tracknum, CODE_WAIT, nlen);
	}
	break;
      case '>':			/* オクターブ上げ */
	if (++octave[tracknum] > OCTAVE_MAX) {
	  octave_oferr:
	    fprintf(stderr, "%s: %ld: octave overflow\n", infile, inpline);
	    showerrline(line);
	}
	lp++;
	break;
      case '<':			/* オクターブ下げ */
	if (--octave[tracknum] < OCTAVE_MIN) {
	  octave_uferr:
	    fprintf(stderr, "%s: %ld: octave underflow\n", infile, inpline);
	    showerrline(line);
	}
	lp++;
	break;
      case 'y':			/* C6230 レジスタ書き込み */
      case 'Y':
	newlp = get_number(&val, ++lp);
	if (newlp == lp)
	  goto err;
	if (val < REGWR_MIN || val > REGWR_MAX) { /* レジスタ番号チェック */
	    fprintf(stderr, "%s: %ld: illegal register assignment\n", infile, inpline);
	    showerrline(lp);
	}
	reg = val;
#ifdef PDEBUG
	fprintf(stderr, "DEBUG reg %ld\n", reg);
#endif
	lp = newlp;
	if (*lp != REGSEP_CHAR)
	  goto err;
	newlp = get_number(&val, ++lp);
	/* レジスタ書き込み値が指定されていないか無効な値か調べる */
	if (newlp == lp || val < REGVAL_MIN || val > REGVAL_MAX) {
	    fprintf(stderr, "%s: %ld: illegal value assignment\n", infile, inpline);
	    showerrline(lp);
	}
	regval = val;
#ifdef PDEBUG
	fprintf(stderr, "DEBUG regval %ld\n", regval);
#endif
	lp = newlp;
	putbuf(tracknum, CODE_REGWR | reg, regval);
	break;
      case 'z':			/* 無限ループ */
      case 'Z':
	putbuf(tracknum, CODE_INFLOOP);
	lp++;
	break;
      default:
      err:
	fprintf(stderr, "%s: %ld: syntax error\n", infile, inpline);
	showerrline(lp);
	/* NOTREACHED */
    }
    return lp;
}

/****************************************
  行解析
****************************************/
void
parse(char *const inbuf)
{
    char *p = inbuf;
    nowline = inbuf;

    /* 先頭文字がコメントなら解析終了 */
    switch (*p) {
      case COMMENT_CHAR:
      case COMMENT_CHAR1:
      case COMMENT_CHAR2:
      case CR_CHAR:
      case LF_CHAR:
	return;
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
	tracknum = *inbuf - '0';
	break;
      default:
	fprintf(stderr, "%s: %ld: illegal track number[%c]\n", infile, inpline, *p);
	exit(EXIT_FAILURE);
    }

    /* 空白文字をスキップ */
    ++p;
    while (*p) {
	while (ISSPACE(*p))
	  p++;
	if (*p == EOL)
	  break;
	p = get_command(p);
    }
}

/****************************************
  行解析初期化
****************************************/
void
parse_init()
{
    int track;

    for (track = TRACK_MIN; track < MAXTRACK; track++) {
	octave[track] = DEFAULT_OCTAVE;
	notelen[track] = DEFAULT_NOTELEN;
	chvol[track] = DEFAULT_CHVOL;
	quant[track] = DEFAULT_QUANT;
    }
}
